package de.stefanocke.roo.xtend

import java.lang.reflect.Modifier
import org.springframework.roo.classpath.PhysicalTypeCategory
import org.springframework.roo.classpath.details.AbstractMemberHoldingTypeDetailsBuilder
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder
import org.springframework.roo.classpath.details.ConstructorMetadata
import org.springframework.roo.classpath.details.ConstructorMetadataBuilder
import org.springframework.roo.classpath.details.FieldMetadataBuilder
import org.springframework.roo.classpath.details.ItdTypeDetailsBuilder
import org.springframework.roo.classpath.details.MethodMetadata
import org.springframework.roo.classpath.details.MethodMetadataBuilder
import org.springframework.roo.classpath.layers.MemberTypeAdditions
import org.springframework.roo.model.JavaSymbolName
import org.springframework.roo.model.JavaType

import static extension de.stefanocke.roo.xtend.RooExtensions.*
import static extension org.springframework.roo.classpath.details.MemberFindingUtils.*

class ItdBuilder extends ImportResolverExtensions {
	
	protected ItdTypeDetailsBuilder itdBuilder
	ClassOrInterfaceTypeDetails govTypeDetails
	Object metadata
	
	def getMetadata(){metadata}
	
	def getGovTypeDetails(){govTypeDetails}

	def init(ItdTypeDetailsBuilder itdBuilder, ClassOrInterfaceTypeDetails govTypeDetails, Object metadata){
		this.itdBuilder = itdBuilder
		this.govTypeDetails = govTypeDetails
		this.metadata = metadata
		init(itdBuilder.importRegistrationResolver)
	}
	
	def build(){
		addMemberTypeAdditions
		extendBaseClass
		implementInterfaces
		buildFields
		buildMethods
		buildInnerTypes
	}
	
	def boolean addMemberTypeAdditions(){
		val memberTypeAdditions = getMemberTypeAdditions()
		if(memberTypeAdditions!=null){
			memberTypeAdditions.forEach [if(it!=null) copyAdditionsTo(itdBuilder, govTypeDetails)]
			true
		} else false
	}
	
	
	def Iterable<MemberTypeAdditions> getMemberTypeAdditions(){
		val single = memberTypeAddition; 
		if (single == null) null else newArrayList(single);
	}
	
	def MemberTypeAdditions getMemberTypeAddition(){
		null
	}
	
	def extendBaseClass() {
		val baseClass = getBaseClass()
		if(baseClass != null ){		
			extendBaseClass(baseClass)
		}
	}
	
	def JavaType getBaseClass(){null}
	
	def extendBaseClass(JavaType baseType){
		if (govTypeDetails.extendsTypes.empty) {
			itdBuilder.addExtendsTypes(baseType);
		}
	}
	
	def implementInterfaces() {
		val interfaces = getInterfaces()
		if(interfaces!=null){
			interfaces.forEach [itdBuilder.addImplementsType(it)]
		}
	}
	
	def Iterable<JavaType> getInterfaces(){
		val singleInterface = getInterface(); 
		if (singleInterface == null) null else newArrayList(singleInterface);
	}
	
	def JavaType getInterface(){null}
	
	def buildFields(){
		buildFields(getFields)
	}
	
	def buildFields(Iterable<FieldMetadataBuilder> fields){
		if(fields!=null){
			for(f:fields){
				itdBuilder.addField(f)
			}		
		}
	}
	
	def Iterable<FieldMetadataBuilder> getFields(){newArrayList()}
	
	def FieldMetadataBuilder field(JavaType type, JavaSymbolName name){
		field(Modifier::PRIVATE, type, name, null)
	}
	def FieldMetadataBuilder field(int mod, JavaType type, JavaSymbolName name, String initializer){
		new FieldMetadataBuilder(itdBuilder.declaredByMetadataId, 
			mod, name, type, initializer)
	}
	
	def buildInnerTypes(){
		buildInnerTypes(getInnerTypes)
	}
	
	def buildInnerTypes(Iterable<ClassOrInterfaceTypeDetailsBuilder> types){
			for(t : types){
				itdBuilder.addInnerType(t.build)
			}	
	}
	
	def Iterable<ClassOrInterfaceTypeDetailsBuilder> getInnerTypes(){newArrayList()}
	
	def ClassOrInterfaceTypeDetailsBuilder innerClass(String shortname){
		innerClass(Modifier::STATIC + Modifier::PUBLIC, shortname)
	}
	
	def ClassOrInterfaceTypeDetailsBuilder innerClass(int mod,String shortname){
		new ClassOrInterfaceTypeDetailsBuilder(itdBuilder.declaredByMetadataId, 
			mod, 
			(govTypeDetails.name.fullyQualifiedTypeName+"."+shortname).javaType,
			PhysicalTypeCategory::CLASS
		)
	}
	
	def buildMethods(){
		buildMethods(itdBuilder, getMethods, true)
	}
	def buildMethods(AbstractMemberHoldingTypeDetailsBuilder amhtdBuilder, Iterable<Method> methods, boolean considerGovernor){
		for(m : methods){
			if(m!=null){
					
				var existingMethod = 
					if(considerGovernor) 
					getMethodOrConstructor(govTypeDetails, m) 
					else null
				
				if(existingMethod==null || !m.signature.constructor){
					var mm = buildMethodOrConstructor(m, existingMethod!=null)
					switch mm {
						MethodMetadata : amhtdBuilder.addMethod(mm)
						ConstructorMetadata : amhtdBuilder.addConstructor(mm)
					}
				}
			}
		}
	}
	
	def Iterable<Method> getMethods(){newArrayList()}
	
	def getMethodOrConstructor(ClassOrInterfaceTypeDetails govTypeDetails, Method method){
		val sig = method.signature
		val paramTypes = sig.parameters.map(p | p.type)	
		if(sig.constructor){ 
			govTypeDetails.getDeclaredConstructor(paramTypes)
			
		} else {
			govTypeDetails.getDeclaredMethod(sig.name, paramTypes)
		}
	}
	def buildMethodOrConstructor(Method method, boolean genPrefix){
		if(method.signature.constructor){ 
			buildConstructor(method)
		} else {
			buildMethod(method, genPrefix)
		}
	}
	
	def buildMethod(Method method, boolean genPrefix){
		val declaringId = itdBuilder.declaredByMetadataId
		val sig = method.signature
		var name = sig.name
		//prepend "gen", if the user provides the method. ... Greetings to EMF :)
		if(genPrefix){
			name = ("gen"+name.symbolName.toFirstUpper).symbol
		}
		val annoParamTypes = sig.parameters.map(p | p.type.annoJavaType(p.annotations.map(ab | ab.build)))
		val paramNames = sig.parameters.map(p | p.name)
		val mmdbuilder = new MethodMetadataBuilder(
			declaringId, sig.modifier,
			name, sig.returnType,
			annoParamTypes,
			paramNames,
			new FakedBodyBuilder(method.body.apply))	
		mmdbuilder.setAnnotations(sig.annotations)
		mmdbuilder.setThrowsTypes(sig.exceptions)
		mmdbuilder.build

	}	
	
	def buildConstructor(Method method){
		val declaringId = itdBuilder.declaredByMetadataId
		val sig = method.signature
		val annoParamTypes = sig.parameters.map(p | p.type.annoJavaType(p.annotations.map(ab | ab.build)))
		val paramNames = sig.parameters.map(p | p.name)
		val cmbuilder = new ConstructorMetadataBuilder(declaringId)
		cmbuilder.setModifier(sig.modifier)
		cmbuilder.setAnnotations(sig.annotations)
		cmbuilder.setParameterTypes(annoParamTypes)
		cmbuilder.setParameterNames(paramNames)
		cmbuilder.setThrowsTypes(sig.exceptions)
		cmbuilder.setBodyBuilder(new FakedBodyBuilder(method.body.apply))
		cmbuilder.build
			
		
	}
	
	def addConstructor(ClassOrInterfaceTypeDetailsBuilder citd, Method method){
		citd.addConstructor(method.buildConstructor)
	}	
	
	/**
	 * Creates code to access the constant value representing the class of the given java type.
	 * Usually, this is just the "class" field. For primitive types, it is the "TYPE" field of the according
	 * wrapper class
	 */
	def classConstant(JavaType type){
		if(type.primitive){
			'''«type.wrapIfPrimitive.resolveStatic».TYPE'''
		} else {
			'''«type.resolveStatic».class'''
		}
	}
	
}